import json

from DrissionPage import ChromiumPage, ChromiumOptions
from DrissionPage.errors import *
from lxml import etree
from bs4 import BeautifulSoup
import time
from concurrent.futures import ThreadPoolExecutor
from future.backports.datetime import datetime
from utils.log_util import Logger
from utils.sqlUtil import ItemToSQLite
import re
logger = Logger().get_logger('log')
class NotMainError(Exception):
    pass
# 进入子页面
class Crawler:
    def __init__(self, *args, **kwargs):
        self.conf = args[0]
        self.index = 0
        browser_path = self.conf["browser"]
        ChromiumOptions().set_browser_path(browser_path).save()
        self.page = ChromiumPage()
        self.phone_number_list = self.conf["phone_list"]
        self.table_name = self.conf["table_name"]
        self.concurrency = self.conf["并发进程数"]
        self.interval = self.conf["每并发任务间隔时间"]
        self.store = ItemToSQLite(db_path="db.db", tb_name=self.table_name)
    def loggin(self):
        self.login_tab = self.page.latest_tab
        self.login_tab.get('http://proxy.crm.he.ctc.com:9500/portal-web/portal/login')

    def search_phone1(self, tab, phone_number="13373000298"):
        """按手机号进入详情页"""
        meta = {}
        meta["手机号"] = phone_number
        tab.get("http://proxy.crm.he.ctc.com:9500/crm/assist/vector?assist=index")
        tab.wait.load_start()
        tab.actions.click('#qryCustomerInputStr').input(phone_number)
        tab.wait.load_start()
        tab.actions.click('.glyphicon icon-maxsearch')
        tab.wait.load_start()
        # 判断是否为主成员
        try:
            is_main = tab.ele('xpath://*[@id="prod_inst_info"]').text
            if "主成员" not in is_main:
                raise NotMainError("非主成员")
            else:
                meta["用户角色"] = "主成员"
        except NotMainError:
            raise NotMainError("非主成员")
        except Exception as e:
            logger.error(e)
            return meta
        # 采集本页字段：
        # 1.1 归属地
        try:
            if self.conf.get("归属地"):
                meta["归属地"] = tab.ele('xpath://*[@id="userQuick"]/div/div[2]/div[2]/span[2]').text
        except Exception as e:
            logger.error(e)
        # 1.2 余额
        try:
            if self.conf.get("余额"):
                meta["余额"] = tab.ele('xpath://*[@id="custCostInfo_dccTotalBalanceAvl"]').text
        except Exception as e:
            logger.error(e)
        # 1.3 付费方式
        try:
            if self.conf.get("付费方式"):
                meta["付费方式"] = tab.ele('xpath://*[@id="userQuick"]/div/div[2]/div[5]/span[2]').text
        except Exception as e:
            logger.error(e)
        # 1.4 入网时间
        try:
            if self.conf.get("入网时间"):
                meta["入网时间"] = tab.ele('xpath://*[@id="userQuick"]/div/div[2]/div[3]/span[2]').text
        except Exception as e:
            logger.error(e)
        # 1.5 服务状态
        try:
            if self.conf.get("服务状态"):
                meta["服务状态"] = tab.ele('xpath://*[@id="userQuick"]/div/div[2]/div[6]/span[2]').text
        except Exception as e:
            logger.error(e)
        # 1.6 副卡数量
        try:
            if self.conf.get("副卡数量"):
                side_deck_text = tab.ele('xpath://*[@id="chooseAll"]').next().text
                meta["副卡数量"] = len(re.findall(r'副成员', side_deck_text))
        except Exception as e:
            logger.error(e)
        # 1.7 融合类型
        try:
            if self.conf.get("融合类型"):
                side_deck_text = tab.ele('xpath://*[@id="chooseAll"]').next().text
                # if "宽带" in side_deck_text and "IPTV" in side_deck_text:
                #     meta["融合类型"] = "含宽带显示"
                # elif "宽带" in side_deck_text and "IPTV" not in side_deck_text:
                #     meta["融合类型"] = "含宽带和IPTV"
                # elif "宽带" not in side_deck_text and "IPTV" in side_deck_text:
                #     meta["融合类型"] = "含IPTV"
                # else:
                #     meta["融合类型"] = ""
                if "宽带" in side_deck_text:
                    meta["融合类型"] = "含宽带"
                else:
                    meta["融合类型"] = "单手机"
        except Exception as e:
            logger.error(e)
        # 2.获取用户画像
        iframe = tab.get_frame(2)
        # 2.1 光猫型号
        try:
            if self.conf.get("光猫型号"):
                meta["光猫型号"] = iframe.ele('text=光猫型号').next().text
        except Exception as e:
            logger.error(e)
        # 2.2 带宽
        try:
            if self.conf.get("带宽"):
                meta["带宽"] = iframe.ele('text=带宽').next().text
        except Exception as e:
            logger.error(e)
        # 2.3 近三月平均
        try:
            if self.conf.get("近3个月账户平均出账"):
                meta["近3个月账户平均出账"] = iframe.ele('text=近3个月账户平均出账').next().text
        except Exception as e:
            logger.error(e)
        # 3.采集主号码销售品信息
        for i in range(5):
            try:
                main_phone_ele = tab.ele('xpath://*[@id="chooseAll"]').next().ele('text:主成员').after(index=2)
                main_phone_frame = main_phone_ele.click.for_new_tab()
                # main_phone_frame.wait.load_start()
                # main_phone_frame.ele('xpath://*[@id="accountTab1"]/li[3]/a').click()  # 点击销售品信息
                main_phone_frame.ele('text=销售品信息').click()  # 点击销售品信息
                # main_phone_frame.wait.load_start()
                main_phone_divs = main_phone_frame.ele('text=附属销售品').next().eles('xpath:div')
                all_info = []
                for main_phone_div in main_phone_divs.filter.displayed():  # 筛选出显示的元素列表并逐个打印文本
                    if not self.conf["主号码销售品信息"]:
                        all_info.append(main_phone_div.text)
                    else:
                        for sale_info in self.conf["主号码销售品信息"]:
                            if sale_info in main_phone_div.text:
                                if self.conf["主号码销售品信息-排除项"]:
                                    for not_sale_info in self.conf["主号码销售品信息-排除项"]:
                                        if not_sale_info in main_phone_div.text:
                                            meta[f"主号码-{sale_info}"] = ""
                                            break
                                        else:
                                            meta[f"主号码-{sale_info}"] = main_phone_div.text
                                else:
                                    meta[f"主号码-{sale_info}"] = main_phone_div.text
                if not self.conf["主号码销售品信息"]:
                    meta["主号码销售品信息"] = " | ".join(all_info)
                break
            except Exception as e:
                logger.error(e)
                continue
            finally:
                try:
                    main_phone_frame.close()
                except:
                    pass
        # 4.采集基础有线宽带销售品信息
        for i in range(5):
            try:
                basic_wireline_ele = tab.ele('xpath://*[@id="chooseAll"]').next().ele('text:基础有线宽带').after(index=2)
                basic_wireline_frame = basic_wireline_ele.click.for_new_tab()
                # basic_wireline_frame.wait.load_start()
                # basic_wireline_frame.ele('xpath://*[@id="accountTab1"]/li[3]/a').click()  # 点击销售品信息
                basic_wireline_frame.ele('text=销售品信息').click()  # 点击销售品信息
                basic_wireline_divs = basic_wireline_frame.ele('text=附属销售品').next().eles('xpath:div')
                all_info = []
                for basic_wireline_div in basic_wireline_divs.filter.displayed():  # 筛选出显示的元素列表并逐个打印文本
                    if not self.conf["宽带号销售品信息"]:
                        all_info.append(basic_wireline_div.text)
                    for sale_info in self.conf["宽带号销售品信息"]:
                        if sale_info in basic_wireline_div.text:
                            if self.conf["宽带号销售品信息-排除项"]:
                                for not_sale_info in self.conf["宽带号销售品信息-排除项"]:
                                    if not_sale_info in basic_wireline_div.text:
                                        meta[f"宽带号码-{sale_info}"] = ""
                                        break
                                    else:
                                        meta[f"宽带号码-{sale_info}"] = basic_wireline_div.text
                            else:
                                meta[f"宽带号码-{sale_info}"] = basic_wireline_div.text
                if not self.conf["宽带号销售品信息"]:
                    meta["宽带号销售品信息"] = " | ".join(all_info)
                break
            except Exception as e:
                logger.error(e)
                continue
            finally:
                try:
                    basic_wireline_frame.close()
                except:
                    pass
        return meta
    def search_phone2(self, tab, phone_number="17733025080"):
        """余额管理"""
        meta = {}
        tab.get("http://proxy.crm.he.ctc.com:9500/portal-web/main/mainPage")
        tab.wait.load_start()
        try:
            tab.actions.move_to('#bulletinBut').click()
            tab.wait.load_start()
        except:
            pass
        # 点击菜单
        tab.ele("xpath:/html/body/div[1]/div[4]/div[1]/ul/li/a/i").click()
        tab.wait.load_start()
        # 点击账务受理
        tab.ele("text:账务受理").click()
        tab.wait.load_start()
        # 点击现金缴费
        tab.ele('xpath://*[@id="main6-1"]/div[1]/a').click()
        tab.wait.load_start()
        # 获取iframe
        for i in range(5):
            try:
                iframe = tab.get_frame("@src:payfee")
                # 输入号码
                iframe.actions.click("@placeholder=请输入号码").input(phone_number)
                tab.wait.load_start()
                # 确认查询
                iframe.actions.click("text=查询")
                tab.wait.load_start()
                break
            except Exception as e:
                time.sleep(2)
                continue
        try:
            rows = iframe.eles("@class=el-table__row")
            for row in rows:
                status = row.ele('xpath:td[5]').text  # 获取账户状态列的内容
                if status == '有效':
                    radio = row.ele('xpath:td[1]')  # 找到对应的勾选框
                    radio.click()  # 选中勾选框
                    break
            if rows:
                iframe.ele('text=确定').click()
        except Exception as e:
            logger.error(e)
            pass
        iframe.wait.load_start()
        # 点击话费使用
        iframe.ele('text=话费使用').click()
        iframe.wait.load_start()
        # 采集近三月信息
        # 获取当前月份的数字
        current_month = datetime.now().month
        # 中文月份映射表
        chinese_months = ["一月", "二月", "三月", "四月", "五月", "六月",
                          "七月", "八月", "九月", "十月", "十一月", "十二月"]
        current_today = chinese_months[current_month - 1]
        for i in range(3):
            iframe.ele('text=查询月份').next().click()
            iframe.wait.load_start()
            mon = iframe.ele(f"text={current_today}").parent(2).prev(i+1)
            mon_text = mon.text
            mon.click()
            iframe.wait.load_start()
            iframe.eles("text=查询")[1].click()
            iframe.wait.load_start()
            if self.conf["套餐使用查询"]:
                table_div = iframe.ele("text=套餐使用查询记录").parent().next()
                # meta[f"{mon_text}_套餐使用查询"] = table_div.html
                meta[f"{mon_text}_套餐使用查询"] = self.parse_html_to_json_1(table_div.html)
            if self.conf["套餐费用明细"]:
                table_div = iframe.ele("text=费用明细").parent().next()
                # meta[f"{mon_text}_套餐费用明细"] = table_div.html
                meta[f"{mon_text}_套餐费用明细"] = self.parse_html_to_json_2(table_div.html)
        return meta
    def single_task(self, phone_number):
        logger.info(f"执行号码任务：【{phone_number}】")
        for i in range(5):
            try:
                tab1 = self.page.new_tab()
                meta1 = self.search_phone1(tab1, phone_number)
                break
            except NotMainError:
                logger.error(f"号码【{phone_number}】非主成员，结束任务")
                self.store.process_item(item={"手机号":phone_number, "用户角色":"非主成员"})
                return
            except Exception as e:
                meta1 = {"手机号": phone_number}
                logger.error(f"号码【{phone_number}】采集详情或销售品信息失败,重试【{i+1}】次：\n{e}")
                continue
            finally:
                tab1.close()
        logger.info(f"号码【{phone_number}】采集详情信息成功，继续采集余额信息...")
        # logger.info(json.dumps(meta1, ensure_ascii=False, indent=4))
        for i in range(10):
            try:
                tab2 = self.page.new_tab()
                meta2 = self.search_phone2(tab2, phone_number)
                break
            except Exception as e:
                logger.error(f"号码【{phone_number}】采集余额信息失败,重试【{i+1}】次：\n{e}")
                meta2 = {}
                continue
            finally:
                tab2.close()
        logger.info(f"号码【{phone_number}】采集余额信息成功...")
        meta = meta1 | meta2
        logger.info(f"号码【{phone_number}】汇总信息:::")
        meta["入库时间"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        logger.info(json.dumps(meta, ensure_ascii=False, indent=4))
        # todo 数据入库
        self.store.process_item(item=meta)

    def run(self):
        self.index = 0
        total = len(self.phone_number_list)
        while True:
            if self.index + self.concurrency < total:
                company_list = self.phone_number_list[self.index:self.index + self.concurrency]
            else:
                company_list = self.phone_number_list[self.index:]
            with ThreadPoolExecutor(max_workers=self.concurrency) as pool:
                pool.map(self.single_task, company_list)
            if self.index > total:
                break
            self.index += self.concurrency
            time.sleep(self.interval)
        self.login_tab.close()
    def parse_html_to_json_1(self, html_content):
        soup = BeautifulSoup(html_content, 'lxml')
        # 查找所有表格行
        rows = soup.select('table.el-table__body tr.el-table__row')
        # 存储解析后的数据
        result = []
        # 遍历每一行，提取数据
        for i in range(0, len(rows)):
            cells = rows[i].find_all('td')
            if len(cells) >= 8:
                # 提取行中所有单元格的文本内容
                sales_id = cells[1].get_text(strip=True)
                package_name = cells[2].get_text(strip=True)
                sales_type = cells[3].get_text(strip=True)
                service_type_1 = cells[4].get_text(strip=True)
                included_1 = cells[5].get_text(strip=True)
                used_1 = cells[6].get_text(strip=True)
                remaining_1 = cells[7].get_text(strip=True)

                # 存储第一行数据
                result.append({
                    '销售品实例标识': sales_id,
                    '套餐名称': package_name,
                    '销售品类型': sales_type,
                    '业务内容': service_type_1,
                    '套餐包含': included_1,
                    '实际使用': used_1,
                    '剩余量': remaining_1
                })

                # 处理第二行（附加的业务内容）
                if i + 1 < len(rows):
                    next_cells = rows[i + 1].find_all('td')
                    if len(next_cells) >= 8:
                        service_type_2 = next_cells[4].get_text(strip=True)
                        included_2 = next_cells[5].get_text(strip=True)
                        used_2 = next_cells[6].get_text(strip=True)
                        remaining_2 = next_cells[7].get_text(strip=True)

                        # 存储第二行数据
                        result.append({
                            '销售品实例标识': sales_id,
                            '套餐名称': package_name,
                            '销售品类型': sales_type,
                            '业务内容': service_type_2,
                            '套餐包含': included_2,
                            '实际使用': used_2,
                            '剩余量': remaining_2
                        })

        # 去重步骤：将列表中的字典转换为集合，然后再转换回列表
        unique_result = []
        seen = set()
        for entry in result:
            entry_tuple = tuple(entry.items())
            if entry_tuple not in seen:
                seen.add(entry_tuple)
                unique_result.append(entry)
        # print(json.dumps(unique_result, ensure_ascii=False, indent=4))
        return json.dumps(unique_result, ensure_ascii=False)

    def parse_html_to_json_2(self, html_content):
        # 使用 BeautifulSoup 解析 HTML，提高兼容性
        # 解析HTML
        soup = BeautifulSoup(html_content, 'html.parser')
        # 提取表头
        header_row = soup.find('thead').find_all('th')
        headers = [header.get_text(strip=True) for header in header_row]
        # 提取表格行
        rows = soup.find_all('tr', class_='el-table__row')
        data = []
        # 遍历每一行，提取数据
        for row in rows:
            cells = row.find_all('td')
            if cells:  # 确保行不为空
                entry = {headers[i]: cells[i].get_text(strip=True) for i in range(len(cells))}
                data.append(entry)
        # 转换为JSON格式
        json_output = json.dumps(data, ensure_ascii=False)
        return json_output

if __name__ == '__main__':
    browser_path = "./chrome/Chrome.exe"
    ChromiumOptions().set_browser_path(browser_path).save()
    # tab = Chromium().latest_tab



